package picturesender;

import com.gooddies.events.BooleanEvent;
import com.gooddies.events.Lookup;
import com.swingson.SwingsonGuiBuilder;

import java.awt.Color;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.WritableRaster;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.swing.*;

/**
 * @author sad
 */
public class MainForm extends JFrame {

    private DrawPanel drawPanel = new DrawPanel();
    private JPanel colorPanel;
    private JPanel wheelPanel;
    private final Queue<PressedWheel> wheelsQueue = new ConcurrentLinkedQueue<PressedWheel>();
    private boolean isChanged = false;
    private OutputStream outputStream;
    private DataInputStream inputStream;
    private final static int END_MARKER = 0xFAAFABBA;
    private final Object lockObject = new Object();
    private String playEffect;

    public MainForm() throws HeadlessException, IOException {
        setTitle("Test ipad sender emulator");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        SwingsonGuiBuilder.createGuiFromJsonInPackage(this);
        colorPanel.add(createSelector(Color.RED));
        colorPanel.add(createSelector(Color.GREEN));
        colorPanel.add(createSelector(Color.BLUE));
        colorPanel.add(createSelector(Color.YELLOW));
        colorPanel.add(createSelector(Color.CYAN));
        colorPanel.add(createSelector(Color.LIGHT_GRAY));
        colorPanel.add(createSelector(Color.black));
        colorPanel.add(createSelector(Color.PINK));
        colorPanel.add(createSelector(Color.ORANGE));

        wheelPanel.add(createWheel(0));
        wheelPanel.add(createWheel(1));
        wheelPanel.add(createWheel(2));
        wheelPanel.add(createWheel(3));
        wheelPanel.add(createWheel(4));
        wheelPanel.add(createWheel(5));
        wheelPanel.add(createWheel(6));
        Lookup.getDefault().addChangeEvent(Color.class, new Lookup.LookupEvent<Color>() {

            @Override
            public void change(Color oldValue, Color newValue) {
                drawPanel.setDrawColor(newValue);
            }
        });
        drawPanel.setOnDrawEvent(new BooleanEvent() {
            @Override
            public boolean fireEvent(Object aThis) {
                isChanged = true;
                return true;
            }
        });
        initServer();
    }

    private WheelPanel createWheel(int id) {
        final WheelPanel w = new WheelPanel(id);
        w.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (e.getButton() == 1) {
                    w.setSpeed(w.getSpeed() + 1);
                } else if (e.getButton() == 3) {
                    w.setSpeed(w.getSpeed() - 1);
                } else {
                    playEffect = Math.random() > 0.5 ? "effect1" : "effect2";
                    isChanged = true;
                    return;
                }

                isChanged = true;
                PressedWheel wheel = new PressedWheel(((WheelPanel) e.getComponent()).getId(), (byte) w.getSpeed());
                wheelsQueue.offer(wheel);
            }
        });

        return w;
    }

    private ColorSelector createSelector(Color color) {
        ColorSelector selector = new ColorSelector();
        selector.setBackground(color);
        return selector;
    }

    public static int toInt(byte[] bytes, int offset) {
        int ret = 0;
        for (int i = 0; i < 4 && i + offset < bytes.length; i++) {
            ret <<= 8;
            ret |= (int) bytes[i] & 0xFF;
        }
        return ret;
    }

    private int readIntFromStream(InputStream stream) throws IOException {
        byte[] buffer = new byte[4];
        for (int i = 0; i < 4; i++) {
            int result = stream.read();
            if (result == -1) {
                throw new SocketException("Cannot read from socket");
            }
            buffer[i] = (byte) result;
        }
        int value = toInt(buffer, 0);
        return value;
    }

    private byte readByteFromInputStream(InputStream stream) throws IOException {
        int result = stream.read();
        if (result == -1) {
            throw new SocketException("Cannot read from socket");
        }
        return (byte) result;
    }

    private static void skipWrongPacket(InputStream stream) {
        int[] endMark = new int[]{0xFA, 0xAF, 0xAB, 0xBA};
        int index = 0;
        while (true) {
            try {
                int b = stream.read();
                if (b == -1) {
                    return;
                }

                if (endMark[index] == b) {
                    index++;
                    if (index == endMark.length) {
                        break;
                    }
                } else {
                    index = 0;
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void readCommandCycle() {
        while (true) {
            try {
                int size = readIntFromStream(inputStream);
                int nsize = size - 5;
                if (nsize < 0) {
                    System.out.print(" negative size packet " + nsize);
                    continue;
                }

                byte[] buffer = new byte[size - 5];
                int command = readByteFromInputStream(inputStream);

                inputStream.readFully(buffer);
                int endMarker = readIntFromStream(inputStream);
                if (endMarker != END_MARKER) {
                    System.out.println("Received bad packet. End marker is not equals to " + END_MARKER + " but equals " + endMarker + "!!!!!!!");
                    skipWrongPacket(inputStream);
                    continue;
                }

                if (command == 1) {//image
                    showPicture(buffer);
                }
            } catch (SocketException ex) {
                break;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    private void showPicture(byte[] buffer) {
        synchronized (drawPanel.lock) {
            int layer = buffer[0];
            if (layer != 2) {
                System.out.println("received layer " + layer + ". Skipped.");
                return;
            }

            int index = 1;
            WritableRaster raster = drawPanel.getSecondLayer().getRaster();
            int[] pixel = new int[3];
            for (int j = 0; j < 39; j++) {
                for (int i = 0; i < 78; i++, index += 3) {
                    pixel[0] = buffer[index + 0] & 0xFF;
                    pixel[1] = buffer[index + 1] & 0xFF;
                    pixel[2] = buffer[index + 2] & 0xFF;
                    raster.setPixel(i, j, pixel);
                }
            }
        }

        drawPanel.repaint();
    }

    private void initServer() throws IOException {
        try {
            final Socket socket = new Socket("127.0.0.1", 8090);
            outputStream = socket.getOutputStream();
            inputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream()));

            Thread thread = new Thread() {

                @Override
                public void run() {
                    readCommandCycle();
                }
            };
            thread.start();
            Timer timer = new Timer(100, new ActionListener() {
                @Override
                public synchronized void actionPerformed(ActionEvent e) {
                    try {
                        sendNope(outputStream);
                        if (!isChanged) {
                            return;
                        }
                        if (playEffect != null) {
                            sendEffect(outputStream, playEffect);
                            playEffect = null;
                        }
                        sendWheelData(outputStream);
                        byte[] buffer = new byte[39 * 78 * 3];
                        int index = 0;
                        WritableRaster raster = drawPanel.getFirstLayer().getRaster();
                        int[] pixel = new int[4];
                        for (int j = 0; j < 39; j++) {
                            for (int i = 0; i < 78; i++, index += 3) {
                                raster.getPixel(i, j, pixel);
                                buffer[index] = (byte) pixel[0];
                                buffer[index + 1] = (byte) pixel[1];
                                buffer[index + 2] = (byte) pixel[2];
                            }
                        }

                        new SendPacket(1).putByte((byte) 1).putByteBuffer(buffer).sendAndFlush(outputStream);
                        isChanged = false;
                    } catch (SocketException ex) {
                        JOptionPane.showMessageDialog(MainForm.this, "Connection was closed by controller");
                        System.exit(1);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            });

            timer.setCoalesce(true);
            timer.setRepeats(true);
            timer.start();
        } catch (Exception ex) {
            ex.printStackTrace();
            JOptionPane.showMessageDialog(null, "Please start controller first");
            System.exit(0);
        }
    }

    private void sendNope(OutputStream stream) throws IOException {
        synchronized (lockObject) {
            new SendPacket(4).sendAndFlush(stream);
        }
    }

    private void sendEffect(OutputStream stream, String effectName) throws IOException {
        synchronized (lockObject) {
            new SendPacket(8).putString(effectName).sendAndFlush(stream);
        }
    }

    private void sendWheelData(OutputStream stream) throws IOException {
        synchronized (lockObject) {
            PressedWheel[] wheels = wheelsQueue.toArray(new PressedWheel[0]);
            wheelsQueue.clear();
            int size = (short) wheels.length;
            if (size != 0) {
                int WheelCommand = 2;
                SendPacket packet = new SendPacket(WheelCommand);
                packet.putShort((short) size);
                for (int i = 0; i < size; i++) {
                    PressedWheel pw = wheels[i];
                    packet.putShort((short) pw.getId());//wheel id
                    packet.putByte((byte) pw.getSpeed());//speed
                    System.out.println("Send wheel " + pw.getId() + " speed=" + (int) pw.getSpeed());
                }

                packet.sendAndFlush(stream);
            }
        }
    }

    private void sendPlayMusicPacket(String id) throws IOException {
        synchronized (lockObject) {
            try {
                new SendPacket(3).putString(id).putByte((byte) 1).sendAndFlush(outputStream);
            } catch (SocketException ex) {
                JOptionPane.showMessageDialog(MainForm.this, "Connection was closed by controller");
                System.exit(1);
            }
        }
    }

    private void playMusic1() throws IOException {
        sendPlayMusicPacket("1");
    }

    private void playMusic2() throws IOException {
        sendPlayMusicPacket("2");
    }

    private void playMusic3() throws IOException {
        sendPlayMusicPacket("3");
    }

    private void playMusic4() throws IOException {
        sendPlayMusicPacket("4");
    }

    private void playMusic5() throws IOException {
        sendPlayMusicPacket("5");
    }

    private void playMusic6() throws IOException {
        sendPlayMusicPacket("6");
    }

    private void playMusic7() throws IOException {
        sendPlayMusicPacket("7");
    }

    private void playMusic8() throws IOException {
        sendPlayMusicPacket("8");
    }

    private void playMusic9() throws IOException {
        sendPlayMusicPacket("9");
    }

    private void playMusic10() throws IOException {
        sendPlayMusicPacket("10");
    }

    private void playMusic11() throws IOException {
        sendPlayMusicPacket("11");
    }

    private void playMusic12() throws IOException {
        sendPlayMusicPacket("12");
    }

    private void playMusic13() throws IOException {
        sendPlayMusicPacket("13");
    }

    private void playMusic14() throws IOException {
        sendPlayMusicPacket("14");
    }

    private void playMusic15() throws IOException {
        sendPlayMusicPacket("15");
    }

    private void playMusic16() throws IOException {
        sendPlayMusicPacket("16");
    }

    private void playMusic17() throws IOException {
        sendPlayMusicPacket("17");
    }

    private void playMusic18() throws IOException {
        sendPlayMusicPacket("18");
    }

    private void playMusic19() throws IOException {
        sendPlayMusicPacket("19");
    }

    private void playMusic20() throws IOException {
        sendPlayMusicPacket("20");
    }


    private void onStartRecording() throws IOException {
        synchronized (lockObject) {
            try {
                int startRecordCommand = 6;
                SendPacket packet = new SendPacket(startRecordCommand);
                packet.sendAndFlush(outputStream);
            } catch (SocketException ex) {
                JOptionPane.showMessageDialog(MainForm.this, "Connection was closed by controller");
                System.exit(1);
            }
        }
    }

    private void onEndRecording() throws IOException {
        String email = JOptionPane.showInputDialog("Enter email", "dmxtest@mailinator.com");
        if (email == null) {
            return;
        }
        synchronized (lockObject) {
            try {
                int stopRecordCommand = 7;
                SendPacket packet = new SendPacket(stopRecordCommand);
                packet.putString(email);
                packet.sendAndFlush(outputStream);
            } catch (SocketException ex) {
                JOptionPane.showMessageDialog(MainForm.this, "Connection was closed by controller");
                System.exit(1);
            }
        }
    }

    private void onStartRotating0() {
        onStartRotating(0);
    }
    private void onStartRotating1() {
        onStartRotating(1);
    }
    private void onStartRotating2() {
        onStartRotating(2);
    }
    private void onStartRotating3() {
        onStartRotating(3);
    }
    private void onStartRotating4() {
        onStartRotating(4);
    }
    private void onStartRotating5() {
        onStartRotating(5);
    }
    private void onStartRotating6() {
        onStartRotating(6);
    }


    private void onStartRotating(int wheelNumber) {
        String rotatingSpeedString = JOptionPane.showInputDialog("Enter speed, from -4 to 4", "1");
        if (rotatingSpeedString == null) {
            return;
        }
        int speed = Integer.parseInt(rotatingSpeedString);
        if (speed > 4) {
            speed = 4;
        }
        if (speed < -4) {
            speed = -4;
        }
        isChanged = true;
        PressedWheel wheel = new PressedWheel(wheelNumber, (byte) speed);
        wheelsQueue.offer(wheel);
        repaint();
    }

    private void onEndRotating0() {
        isChanged = true;
        PressedWheel wheel = new PressedWheel(0, (byte) 0);
        wheelsQueue.offer(wheel);
    }
    private void onEndRotating1() {
        isChanged = true;
        PressedWheel wheel = new PressedWheel(1, (byte) 0);
        wheelsQueue.offer(wheel);
    }
    private void onEndRotating2() {
        isChanged = true;
        PressedWheel wheel = new PressedWheel(2, (byte) 0);
        wheelsQueue.offer(wheel);
    }
    private void onEndRotating3() {
        isChanged = true;
        PressedWheel wheel = new PressedWheel(3, (byte) 0);
        wheelsQueue.offer(wheel);
    }
    private void onEndRotating4() {
        isChanged = true;
        PressedWheel wheel = new PressedWheel(4, (byte) 0);
        wheelsQueue.offer(wheel);
    }
    private void onEndRotating5() {
        isChanged = true;
        PressedWheel wheel = new PressedWheel(5, (byte) 0);
        wheelsQueue.offer(wheel);
    }
    private void onEndRotating6() {
        isChanged = true;
        PressedWheel wheel = new PressedWheel(6, (byte) 0);
        wheelsQueue.offer(wheel);
    }
}
